﻿/* 
* Copyright (c) 2010, The University of Texas at Austin
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification, 
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, 
* this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice, 
* this list of conditions and the following disclaimer in the documentation and/or other materials 
* provided with the distribution.
*
* Neither the name of The University of Texas at Austin nor the names of its contributors may be 
* used to endorse or promote products derived from this software without specific prior written 
* permission.
* 
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR 
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 
* FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS 
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
* THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Bio.Data.Interfaces;
using Bio.Views.Structure.Editor.Internal.Data;
using Bio.Views.Structure.Editor.Internal.Editing;
using Bio.Views.Structure.Editor.Internal.Options;
using Bio.Views.Structure.Editor.Internal.Rendering.Interfaces;
using Bio.Views.Structure.Editor.Internal.Selection;
using Bio.Views.Structure.Editor.Internal.Selection.Interfaces;
using Bio.Views.Structure.Editor.Internal.Tools;
using Bio.Views.Structure.Editor.Internal.Tools.Interfaces;
using Bio.Views.Structure.Editor.Properties;
using Bio.Views.Structure.Editor.ViewModels;

namespace Bio.Views.Structure.Editor.Internal.Rendering
{
    /// <summary>
    /// This class would likely disappear in any refactoring and most of the visual functionality should move to the SecondaryStructureEditor user control.
    /// </summary>
    public class SecondaryStructureDrawing : FrameworkElement, IStructureElementVisualComponent, IDataDrivenDefaultStructureElementPropertyProvider
    {
        #region Dependency Properties

        public static readonly DependencyProperty NucleotideFontFamilyProperty = DependencyProperty.Register("NucleotideFontFamily",
            typeof(FontFamily), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontWeightProperty = DependencyProperty.Register("NucleotideFontWeight",
            typeof(FontWeight?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontStyleProperty = DependencyProperty.Register("NucleotideFontStyle",
            typeof(FontStyle?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFontSizeProperty = DependencyProperty.Register("NucleotideFontSize",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideRenderingStyleProperty = DependencyProperty.Register("NucleotideRenderingStyle",
            typeof(NucleotideRenderingMode?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideFillProperty = DependencyProperty.Register("NucleotideFill",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty IsNucleotideSubstitutedProperty = DependencyProperty.Register("IsNucleotideSubstituted",
          typeof(bool?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideSubstitutionProperty = DependencyProperty.Register("NucleotideSubstitution",
            typeof(char?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleDiameterProperty = DependencyProperty.Register("NucleotideCircleDiameter",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleEdgeThicknessProperty = DependencyProperty.Register("NucleotideCircleEdgeThickness",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleIsFilledProperty = DependencyProperty.Register("NucleotideCircleIsFilled",
            typeof(bool?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleFillProperty = DependencyProperty.Register("NucleotideCircleFill",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty NucleotideCircleStrokeProperty = DependencyProperty.Register("NucleotideCircleStroke",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorRenderingStyleProperty = DependencyProperty.Register("BasePairConnectorRenderingStyle",
           typeof(BasePairConnectorRenderingMode?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorStrokeProperty = DependencyProperty.Register("BasePairConnectorStroke",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorStrokeThicknessProperty = DependencyProperty.Register("BasePairConnectorStrokeThickness",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorSizeProperty = DependencyProperty.Register("BasePairConnectorSize",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        /*public static readonly DependencyProperty BasePairConnectorOffsetProperty = DependencyProperty.Register("BasePairConnectorOffset",
            typeof(double?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());*/

        public static readonly DependencyProperty BasePairConnectorFillProperty = DependencyProperty.Register("BasePairConnectorFill",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorIsVisibleProperty = DependencyProperty.Register("BasePairConnectorIsVisible",
            typeof(bool?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        public static readonly DependencyProperty BasePairConnectorIsFilledProperty = DependencyProperty.Register("BasePairConnectorIsFilled",
            typeof(bool?), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnComponentVisualPropertyChanged));

        private static void OnComponentVisualPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawingVisual = d as SecondaryStructureDrawing;
            if (drawingVisual != null)
            {
                drawingVisual.PropagateVisualPropertyChanged();
            }
        }

        public static readonly DependencyProperty CurrentStructureModelEditingToolProperty = DependencyProperty.Register("CurrentStructureModelEditingTool",
            typeof(IStructureDrawingTool), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnCurrentStructureModelEditingToolChanged));

        public IStructureDrawingTool CurrentStructureModelEditingTool
        {
            get { return (IStructureDrawingTool)GetValue(CurrentStructureModelEditingToolProperty); }
            set { SetValue(CurrentStructureModelEditingToolProperty, value); }
        }

        private static void OnCurrentStructureModelEditingToolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            if (drawing != null)
            {               
                IStructureDrawingTool prevTool = e.OldValue as IStructureDrawingTool;
                if (prevTool != null)
                {
                    prevTool.IsActive = false;
                    //prevTool.IsEnabled = false;
                }

                IStructureDrawingTool newTool = e.NewValue as IStructureDrawingTool;
                if (newTool != null)
                {
                    newTool.IsActive = true;
                    foreach (var tool in drawing.StructureModelEditingTools)
                        if (tool != newTool)
                            tool.IsActive = false;
                }
            }
        }

        public static readonly DependencyProperty StructureModelEditingToolsProperty = DependencyProperty.Register("StructureModelEditingTools",
            typeof(IEnumerable<IStructureDrawingTool>), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(Enumerable.Empty<IStructureDrawingTool>()));

        public IEnumerable<IStructureDrawingTool> StructureModelEditingTools
        {
            get { return (IEnumerable<IStructureDrawingTool>)GetValue(StructureModelEditingToolsProperty); }
            set { SetValue(StructureModelEditingToolsProperty, value); }
        }

        public static readonly DependencyProperty CurrentStructureDrawingToolProperty = DependencyProperty.Register("CurrentStructureDrawingTool",
            typeof(IStructureDrawingTool), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, OnCurrentStructureDrawingToolChanged));

        public IStructureDrawingTool CurrentStructureDrawingTool
        {
            get { return (IStructureDrawingTool)GetValue(CurrentStructureDrawingToolProperty); }
            set { SetValue(CurrentStructureDrawingToolProperty, value); }
        }

        private static void OnCurrentStructureDrawingToolChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            if (drawing != null)
            {
                IStructureDrawingTool prevTool = e.OldValue as IStructureDrawingTool;
                if (prevTool != null)
                {
                    prevTool.IsActive = false;
                    //prevTool.IsEnabled = false;
                }

                IStructureDrawingTool newTool = e.NewValue as IStructureDrawingTool;
                if (newTool != null)
                {
                    newTool.IsActive = true;
                    foreach (var tool in drawing.StructureDrawingTools)
                        if (tool != newTool)
                            tool.IsActive = false;
                }
            }
        }

        public static readonly DependencyProperty StructureDrawingToolsProperty = DependencyProperty.Register("StructureDrawingTools",
            typeof(IEnumerable<IStructureDrawingTool>), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(Enumerable.Empty<IStructureDrawingTool>()));

        public IEnumerable<IStructureDrawingTool> StructureDrawingTools
        {
            get { return (IEnumerable<IStructureDrawingTool>)GetValue(StructureDrawingToolsProperty); }
            set { SetValue(StructureDrawingToolsProperty, value); }
        }

        public static readonly DependencyProperty BasePairDefaultRenderingOptionSetsProperty = DependencyProperty.Register("BasePairDefaultRenderingOptionSets",
            typeof(BasePairVisualRenderingOptionsDataCollection), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(new BasePairVisualRenderingOptionsDataCollection() { BasePairVisualRenderingOptionsData.CreateDefault(Settings.Default.DefaultBasePairVisualRenderingOptionSetLabel) }, OnBasePairDefaultRenderingOptionSetsChanged));

        private static void OnBasePairDefaultRenderingOptionSetsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawingVisual = d as SecondaryStructureDrawing;
            if (drawingVisual != null)
            {
                drawingVisual.InstallBasePairVisualRenderingDefaults();
                drawingVisual.PropagateVisualPropertyChanged(); //Have to make any inheriting pickup the change.
            }
        }

        public BasePairVisualRenderingOptionsDataCollection BasePairDefaultRenderingOptionSets
        {
            get { return (BasePairVisualRenderingOptionsDataCollection)GetValue(BasePairDefaultRenderingOptionSetsProperty); }
            set { SetValue(BasePairDefaultRenderingOptionSetsProperty, value); }
        }

        public static readonly DependencyProperty NucleotideDefaultRenderingOptionSetsProperty = DependencyProperty.Register("NucleotideDefaultRenderingOptionSets",
            typeof(NucleotideVisualRenderingOptionsDataCollection), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(new NucleotideVisualRenderingOptionsDataCollection() { NucleotideVisualRenderingOptionsData.CreateDefault(Settings.Default.DefaultNucleotideVisualRenderingOptionSetLabel) }, OnNucleotideDefaultRenderingOptionSetsChanged));

        private static void OnNucleotideDefaultRenderingOptionSetsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawingVisual = d as SecondaryStructureDrawing;
            if (drawingVisual != null)
            {
                drawingVisual.InstallNucleotideVisualRenderingDefaults();
                drawingVisual.PropagateVisualPropertyChanged(); //Have to make any inheriting pickup the change.
            }
        }

        public NucleotideVisualRenderingOptionsDataCollection NucleotideDefaultRenderingOptionSets
        {
            get { return (NucleotideVisualRenderingOptionsDataCollection)GetValue(NucleotideDefaultRenderingOptionSetsProperty); }
            set { SetValue(NucleotideDefaultRenderingOptionSetsProperty, value); }
        }

        private static readonly DependencyPropertyKey CurrentBasePairCountPropertyKey = DependencyProperty.RegisterReadOnly("CurrentBasePairCount",
            typeof(int), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());
        public static DependencyProperty CurrentBasePairCountProperty = CurrentBasePairCountPropertyKey.DependencyProperty;

        public int CurrentBasePairCount
        {
            get { return (int)GetValue(CurrentBasePairCountProperty); }
            private set { SetValue(CurrentBasePairCountPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey CurrentHelixCountPropertyKey = DependencyProperty.RegisterReadOnly("CurrentHelixCount",
            typeof(int), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());
        public static DependencyProperty CurrentHelixCountProperty = CurrentHelixCountPropertyKey.DependencyProperty;

        public int CurrentHelixCount
        {
            get { return (int)GetValue(CurrentHelixCountProperty); }
            private set { SetValue(CurrentHelixCountPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey CurrentVisibleNucleotideCountPropertyKey = DependencyProperty.RegisterReadOnly("CurrentVisibleNucleotideCount",
            typeof(int), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());
        public static DependencyProperty CurrentVisibleNucleotideCountProperty = CurrentVisibleNucleotideCountPropertyKey.DependencyProperty;

        public int CurrentVisibleNucleotideCount
        {
            get { return (int)GetValue(CurrentVisibleNucleotideCountProperty); }
            private set { SetValue(CurrentVisibleNucleotideCountPropertyKey, value); }
        }

        public static readonly DependencyProperty StructureDataProperty = DependencyProperty.Register("StructureData",
            typeof(SecondaryStructureEditorViewModel), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null, FrameworkPropertyMetadataOptions.AffectsMeasure | FrameworkPropertyMetadataOptions.AffectsRender, OnStructureDataPropertyChanged));

        public SecondaryStructureEditorViewModel StructureData
        {
            get { return (SecondaryStructureEditorViewModel)GetValue(StructureDataProperty); }
            set { SetValue(StructureDataProperty, value); }
        }

        private static void OnStructureDataPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs args)
        {
            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            if (drawing != null)
            {
                drawing.OnStructureDrawingDataChanged();
            }
        }

        public static readonly DependencyProperty SelectedStructureElementVisualIDProperty = DependencyProperty.Register("SelectedStructureElementVisualID",
            typeof(int), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(-1, FrameworkPropertyMetadataOptions.AffectsRender, OnSelectedStructureElementVisualIDChanged));

        public int SelectedStructureElementVisualID
        {
            get { return (int)GetValue(SelectedStructureElementVisualIDProperty); }
            set { SetValue(SelectedStructureElementVisualIDProperty, value); }
        }

        static void OnSelectedStructureElementVisualIDChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            int? newTargetElementID = e.NewValue as int?;
            int? oldTargetElementID = e.OldValue as int?;

            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            if (drawing != null && newTargetElementID != null && oldTargetElementID != null)
            {
                drawing.OnSelectedElementVisualIDChanged();
            }
        }

        private static readonly DependencyPropertyKey SelectedStructureElementVisualLocationKey = DependencyProperty.RegisterReadOnly("SelectedStructureElementVisualLocation",
            typeof(Point), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty SelectedStructureElementVisualLocationProperty = SelectedStructureElementVisualLocationKey.DependencyProperty;

        public Point SelectedStructureElementVisualLocation
        {
            get { return (Point)GetValue(SelectedStructureElementVisualLocationProperty); }
            private set { SetValue(SelectedStructureElementVisualLocationKey, value); }
        }

        private static readonly DependencyPropertyKey SelectedStructureElementVisualPropertyKey = DependencyProperty.RegisterReadOnly("SelectedStructureElementVisual",
            typeof(object), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata());
        public static readonly DependencyProperty SelectedStructureElementVisualProperty = SelectedStructureElementVisualPropertyKey.DependencyProperty;

        public object SelectedStructureElementVisual
        {
            get { return GetValue(SelectedStructureElementVisualProperty); }
            private set { SetValue(SelectedStructureElementVisualPropertyKey, value); }
        }

        public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register("SelectionMode",
            typeof(StructureElementSelectionMode), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(StructureElementSelectionMode.None, FrameworkPropertyMetadataOptions.AffectsRender, OnSelectionModeChanged));

        public StructureElementSelectionMode SelectionMode
        {
            get { return (StructureElementSelectionMode)GetValue(SelectionModeProperty); }
            set { SetValue(SelectionModeProperty, value); }
        }

        private static void OnSelectionModeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            if (drawing != null)
            {
                drawing.OnSelectionModeChanged();
            }
        }

        public static readonly DependencyProperty DrawingBackgroundProperty = DependencyProperty.Register("DrawingBackground",
            typeof(Brush), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(new SolidColorBrush(Colors.White), FrameworkPropertyMetadataOptions.AffectsRender));

        public Brush DrawingBackground
        {
            get { return (Brush)GetValue(DrawingBackgroundProperty); }
            set { SetValue(DrawingBackgroundProperty, value); }
        }

        private static readonly DependencyPropertyKey DrawingWidthPropertyKey = DependencyProperty.RegisterReadOnly("DrawingWidth",
            typeof(double), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty DrawingWidthProperty = DrawingWidthPropertyKey.DependencyProperty;

        public double DrawingWidth
        {
            get { return (double)GetValue(DrawingWidthProperty); }
            private set { SetValue(DrawingWidthPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DrawingHeightPropertyKey = DependencyProperty.RegisterReadOnly("DrawingHeight",
            typeof(double), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty DrawingHeightProperty = DrawingHeightPropertyKey.DependencyProperty;

        public double DrawingHeight
        {
            get { return (double)GetValue(DrawingHeightProperty); }
            private set { SetValue(DrawingHeightPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey CanvasWidthPropertyKey = DependencyProperty.RegisterReadOnly("CanvasWidth",
            typeof(double), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty CanvasWidthProperty = CanvasWidthPropertyKey.DependencyProperty;

        public double CanvasWidth
        {
            get { return (double)GetValue(CanvasWidthProperty); }
            private set { SetValue(CanvasWidthPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey CanvasHeightPropertyKey = DependencyProperty.RegisterReadOnly("CanvasHeight",
            typeof(double), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(0.0, FrameworkPropertyMetadataOptions.AffectsMeasure));
        public static readonly DependencyProperty CanvasHeightProperty = CanvasHeightPropertyKey.DependencyProperty;

        public double CanvasHeight
        {
            get { return (double)GetValue(CanvasHeightProperty); }
            private set { SetValue(CanvasHeightPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DrawingOffsetPropertyKey = DependencyProperty.RegisterReadOnly("DrawingOffset",
            typeof(Point), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(new Point(0.0, 0.0), FrameworkPropertyMetadataOptions.AffectsArrange));
        public static readonly DependencyProperty DrawingOffsetProperty = DrawingOffsetPropertyKey.DependencyProperty;

        public Point DrawingOffset
        {
            get { return (Point)GetValue(DrawingOffsetProperty); }
            private set { SetValue(DrawingOffsetPropertyKey, value); }
        }

        private static readonly DependencyPropertyKey DrawingSnapshotPropertyKey = DependencyProperty.RegisterReadOnly("DrawingSnapshot",
            typeof(BitmapSource), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(null));
        public static readonly DependencyProperty DrawingSnapshotProperty = DrawingSnapshotPropertyKey.DependencyProperty;

        public BitmapSource DrawingSnapshot
        {
            get { return (BitmapSource)GetValue(DrawingSnapshotProperty); }
            private set { SetValue(DrawingSnapshotPropertyKey, value); }
        }

        public static readonly DependencyProperty DrawingDirtyProperty = DependencyProperty.Register("DrawingDirty",
            typeof(bool), typeof(SecondaryStructureDrawing), new FrameworkPropertyMetadata(false, null, OnCoerceDrawingDirty));

        private static object OnCoerceDrawingDirty(DependencyObject d, object value)
        {
            SecondaryStructureDrawing drawing = d as SecondaryStructureDrawing;
            return drawing.DrawingManager.DirtyStatus;
        }

        public bool DrawingDirty
        {
            get { return (bool)GetValue(DrawingDirtyProperty); }
            set { SetValue(DrawingDirtyProperty, value); }
        }

        #endregion

        #region Routed Events

        public static readonly RoutedEvent SelectedStructureElementVisualIDChangedEvent = EventManager.RegisterRoutedEvent("SelectedStructureElementVisualIDChanged",
            RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SecondaryStructureDrawing));

        public event RoutedEventHandler SelectedStructureElementVisualIDChanged
        {
            add { AddHandler(SelectedStructureElementVisualIDChangedEvent, value); }
            remove { RemoveHandler(SelectedStructureElementVisualIDChangedEvent, value); }
        }

        public static readonly RoutedEvent StructureDataChangedEvent = EventManager.RegisterRoutedEvent("StructureDataChanged",
            RoutingStrategy.Bubble, typeof(RoutedEventHandler), typeof(SecondaryStructureDrawing));

        public event RoutedEventHandler StructureDataChanged
        {
            add { AddHandler(StructureDataChangedEvent, value); }
            remove { RemoveHandler(StructureDataChangedEvent, value); }
        }

        #endregion

        #region IDataDrivenDefaultStructureElementPropertyProvider Members and Implementation

        public void NotifyStartingNucleotidePropertyResolution(IBioSymbol data)
        {
            NucleotideResolvingData = data;
        }

        public void NotifyEndingNucleotidePropertyResolution()
        {
            NucleotideResolvingData = null;
        }

        public void NotifyStartingBasePairPropertyResolution(IBasePairBioEntity data)
        {
            BasePairResolvingData = data;
        }

        public void NotifyEndingBasePairPropertyResolution()
        {
            BasePairResolvingData = null;
        }

        private IBioSymbol NucleotideResolvingData { get; set; }
        private IBasePairBioEntity BasePairResolvingData { get; set; }

        #endregion

        #region IStructureElementVisualComponent Members

        public IEnumerable<int> NucleotideIndices
        {
            get { return Enumerable.Empty<int>(); }
        }

        public IStructureElementVisualComponent ParentComponent
        {
            get { return null; }
            set { }
        }

        public IEnumerable<IStructureElementVisualComponent> Children
        {
            get { return Enumerable.Empty<IStructureElementVisualComponent>(); }
        }

        public bool IsSelected
        {
            get { return false; }
        }

        public bool IsComponentVisible
        {
            get { return true; }
            set { }
        }

        public FontFamily NucleotideFontFamily
        {
            get
            {
                FontFamily val = GetValue(NucleotideFontFamilyProperty) as FontFamily;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontFamily;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideFontFamily;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideFontFamily;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontFamily = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideFontFamilyProperty, value);
                //UninterruptEditNotification();
            }
        }

        public FontWeight? NucleotideFontWeight
        {
            get
            {
                FontWeight? val = GetValue(NucleotideFontWeightProperty) as FontWeight?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontWeight;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideFontWeight;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideFontWeight;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontWeight = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideFontWeightProperty, value);
                //UninterruptEditNotification();
            }
        }

        public FontStyle? NucleotideFontStyle
        {
            get
            {
                FontStyle? val = GetValue(NucleotideFontStyleProperty) as FontStyle?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontStyle;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideFontStyle;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideFontStyle;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontStyle = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideFontStyleProperty, value);
                //UninterruptEditNotification();
            }
        }

        public double? NucleotideFontSize
        {
            get
            {
                double? val = GetValue(NucleotideFontSizeProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFontSize;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideFontSize;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideFontSize;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFontSize = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideFontSizeProperty, value);
                //UninterruptEditNotification();
            }
        }

        public NucleotideRenderingMode? NucleotideRenderingStyle
        {
            get
            {
                NucleotideRenderingMode? val = GetValue(NucleotideRenderingStyleProperty) as NucleotideRenderingMode?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideRenderingStyle;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideRenderingStyle;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideRenderingStyle;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideRenderingStyle = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideRenderingStyleProperty, value);
                //UninterruptEditNotification();
            }
        }

        public Brush NucleotideFill
        {
            get
            {
                Brush val = GetValue(NucleotideFillProperty) as Brush;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideFill;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideFill;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideFill;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideFill = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideFillProperty, value);
                //UninterruptEditNotification();
            }
        }

        public char? NucleotideSubstitution 
        {
            get
            {
                char? val = GetValue(NucleotideSubstitutionProperty) as char?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideSubstitution;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideSubstitution;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideSubstitution;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideSubstitution = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideSubstitutionProperty, value);
                //UninterruptEditNotification();
            } 
        }

        public bool? IsNucleotideSubstituted 
        {
            get
            {
                bool? val = GetValue(IsNucleotideSubstitutedProperty) as bool?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.IsNucleotideSubstituted;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].IsNucleotideSubstituted;
                        }
                        return NucleotideDefaultRenderingOptions.IsNucleotideSubstituted;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.IsNucleotideSubstituted = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(IsNucleotideSubstitutedProperty, value);
                //UninterruptEditNotification();
            }
        }

        public double? NucleotideCircleDiameter 
        {
            get
            {
                double? val = GetValue(NucleotideCircleDiameterProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleDiameter;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideCircleDiameter;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideCircleDiameter;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleDiameter = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideCircleDiameterProperty, value);
                //UninterruptEditNotification();
            }
        }

        public double? NucleotideCircleEdgeThickness 
        {
            get
            {
                double? val = GetValue(NucleotideCircleEdgeThicknessProperty) as double?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleEdgeThickness;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideCircleEdgeThickness;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideCircleEdgeThickness;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleEdgeThickness = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideCircleEdgeThicknessProperty, value);
                //UninterruptEditNotification();
            } 
        }

        public bool? NucleotideCircleIsFilled 
        {
            get
            {
                bool? val = GetValue(NucleotideCircleIsFilledProperty) as bool?;
                if (val.HasValue)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleIsFilled;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideCircleIsFilled;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideCircleIsFilled;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleIsFilled = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideCircleIsFilledProperty, value);
                //UninterruptEditNotification();
            }
        }

        public Brush NucleotideCircleFill 
        {
            get
            {
                Brush val = GetValue(NucleotideCircleFillProperty) as Brush;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleFill;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideCircleFill;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideCircleFill;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleFill = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideCircleFillProperty, value);
                //UninterruptEditNotification();
            }
        }

        public Brush NucleotideCircleStroke 
        {
            get
            {
                Brush val = GetValue(NucleotideCircleStrokeProperty) as Brush;
                if (val != null)
                {
                    return val;
                }
                else
                {
                    //If were not explicitly set, we look up the food chain.
                    if (ParentComponent != null)
                        return ParentComponent.NucleotideCircleStroke;
                    else
                    {
                        if (NucleotideResolvingData != null)
                        {
                            if (NucleotideDefaultRenderingOptionsByValue.ContainsKey(NucleotideResolvingData.Value))
                                return NucleotideDefaultRenderingOptionsByValue[NucleotideResolvingData.Value].NucleotideCircleStroke;
                        }
                        return NucleotideDefaultRenderingOptions.NucleotideCircleStroke;
                    }
                }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.NucleotideCircleStroke = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(NucleotideCircleStrokeProperty, value);
                //UninterruptEditNotification();
            }
        }

        public bool? BasePairConnectorIsFilled 
        {
            get
            {
                bool? val = GetValue(BasePairConnectorIsFilledProperty) as bool?;
                if (!val.HasValue)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorIsFilled;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorIsFilled;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorIsFilled;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorIsFilled = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorIsFilledProperty, value);
                //UninterruptEditNotification();
            }
        }

        public bool? BasePairConnectorIsVisible 
        {
            get
            {
                bool? val = GetValue(BasePairConnectorIsVisibleProperty) as bool?;
                if (!val.HasValue)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorIsVisible;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorIsVisible;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorIsVisible;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorIsVisible = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorIsVisibleProperty, value);
                //UninterruptEditNotification();
            } 
        }

        public Brush BasePairConnectorFill 
        {
            get
            {
                Brush val = GetValue(BasePairConnectorFillProperty) as Brush;
                if (val == null)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorFill;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorFill;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorFill;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorFill = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorFillProperty, value);
                //UninterruptEditNotification();
            }
        }

        public Brush BasePairConnectorStroke 
        {
            get
            {
                Brush val = GetValue(BasePairConnectorStrokeProperty) as Brush;
                if (val == null)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorStroke;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorStroke;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorStroke;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorStroke = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorStrokeProperty, value);
                //UninterruptEditNotification();
            }
        }

        public double? BasePairConnectorSize 
        {
            get
            {
                double? val = GetValue(BasePairConnectorSizeProperty) as double?;
                if (!val.HasValue)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorSize;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorSize;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorSize;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorSize = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorSizeProperty, value);
                //UninterruptEditNotification();
            }
        }
        
        public double? BasePairConnectorStrokeThickness 
        {
            get
            {
                double? val = GetValue(BasePairConnectorStrokeThicknessProperty) as double?;
                if (!val.HasValue)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorStrokeThickness;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorStrokeThickness;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorStrokeThickness;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorStrokeThickness = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorStrokeThicknessProperty, value);
                //UninterruptEditNotification();
            }
        }

        public BasePairConnectorRenderingMode? BasePairConnectorRenderingStyle 
        {
            get
            {
                BasePairConnectorRenderingMode? val = GetValue(BasePairConnectorRenderingStyleProperty) as BasePairConnectorRenderingMode?;
                if (!val.HasValue)
                {
                    try
                    {
                        if (ParentComponent != null)
                            return ParentComponent.BasePairConnectorRenderingStyle;
                    }
                    catch (NotSupportedException) { }
                    if (BasePairResolvingData != null)
                    {
                        if (BasePairDefaultRenderingOptionsByValue.ContainsKey(BasePairResolvingData.Text))
                            return BasePairDefaultRenderingOptionsByValue[BasePairResolvingData.Text].BasePairConnectorRenderingStyle;
                    }
                    return BasePairDefaultRenderingOptions.BasePairConnectorRenderingStyle;
                }
                else { return val; }
            }
            set 
            {
                /*if (PropertyContextManager != null && !PauseEditTracking)
                {
                    PropertyContextManager.BasePairConnectorRenderingStyle = value;
                    OnStructureDrawingVisualEdited();
                }
                InterruptEditNotification(); //Hack....Override change to dependency property propagating as an edit.*/
                SetValue(BasePairConnectorRenderingStyleProperty, value);
                //UninterruptEditNotification();
            }
        }

        public Visual VisualComponent
        {
            get { return this; }
        }

        public void NotifyParentHierarchyChanged()
        {
            throw new NotImplementedException();
        }

        public void PropagateVisualPropertyChanged()
        {
            foreach (var child in ChildrenInternal)
                child.PropagateVisualPropertyChanged();
        }

        public void AddChild(IStructureElementVisualComponent child)
        {
            if (child != null && !ChildrenInternal.Contains(child))
            {
                ChildrenInternal.Add(child);
                Visuals.Add(child.VisualComponent);
                RecursivelyAddChildVisualsToVisualCollection(child);
                child.ParentComponent = this;
            }
        }       

        public void RemoveChild(IStructureElementVisualComponent child)
        {
            if (child != null && ChildrenInternal.Contains(child))
            {
                ChildrenInternal.Remove(child);
                Visuals.Remove(child.VisualComponent);
                RecursivelyRemoveChildVisualsFromVisualCollection(child);
                child.ParentComponent = null;
            }
        }

        public Visual MakeVisualSnapshot()
        {
            throw new NotImplementedException();
        }

        public bool HitTest(Point point, bool includeChildren)
        {
            throw new NotImplementedException();
        }

        public void MakeSelectable()
        {
        }

        public void MakeUnselectable()
        {
        }

        public void Select(bool showNucleotideConnectors)
        {
        }

        public void Deselect()
        {
        }

        public void ContextuallySelect()
        {
        }

        public void ContextuallyDeselect()
        {
        }

        public bool IsNucleotideFillInheriting()
        {
            if (StructureData == null)
                throw new ArgumentException(string.Format("Error: No data to check for inheritance"));
            return StructureData.IsNucleotideFillInheriting();
        }

        public bool IsNucleotideCircleFillInheriting()
        {
            if(StructureData == null)
                throw new ArgumentException(string.Format("Error: No data to check for inheritance"));
            return StructureData.IsNucleotideCircleFillInheriting();
        }

        public bool IsNucleotideCircleStrokeInheriting()
        {
            if (StructureData == null)
                throw new ArgumentException(string.Format("Error: No data to check for inheritance"));
            return StructureData.IsNucleotideCircleStrokeInheriting();
        }

        public bool IsBasePairConnectorFillInheriting()
        {
            if (StructureData == null)
                throw new ArgumentException(string.Format("Error: No data to check for inheritance"));
            return StructureData.IsBasePairConnectorFillInheriting();
        }

        public bool IsBasePairConnectorStrokeInheriting()
        {
            if (StructureData == null)
                throw new ArgumentException(string.Format("Error: No data to check for inheritance"));
            return StructureData.IsBasePairConnectorStrokeInheriting();
        }

        public void StartEditTracking()
        {
            if(StructureData != null)
                StructureData.StartEditTracking();
        }

        public void InterruptEditTracking()
        {
            if(StructureData != null)
                StructureData.InterruptEditTracking();
        }

        public void UninterruptEditTracking()
        {
            if(StructureData != null)
                StructureData.UninterruptEditTracking();
        }

        public void InterruptEditNotification()
        {
            if(StructureData != null)
                StructureData.InterruptEditNotification();
        }

        public void UninterruptEditNotification()
        {
            if(StructureData != null)
                StructureData.UninterruptEditNotification();
        }

        public void ApplyPropertyEditSet(StructureElementPropertyChangeSet set, bool interruptEditTracking)
        {
            if (StructureData != null)
                StructureData.ApplyPropertyEditSet(set, interruptEditTracking);
        }

        public void UndoAllPropertyChanges()
        {
            throw new NotImplementedException();
        }

        public void UndoLastPropertyChange()
        {
            if(StructureData != null)
                StructureData.UndoLastPropertyChange();
        }

        public object SynchronizeProperties()
        {
            if (StructureData != null)
                return StructureData.SynchronizeProperties();
            return null;
        }

        public object SaveProperties()
        {
            if (StructureData != null)
                return StructureData.SaveProperties();
            return null;
        }

        public bool IsDirty()
        {
            return StructureData.IsDirty();
        }

        #endregion

        public Dictionary<char, NucleotideVisualRenderingOptions> NucleotideDefaultRenderingOptionsByValue { get; private set; }
        public NucleotideVisualRenderingOptions NucleotideDefaultRenderingOptions { get; private set; }

        public Dictionary<string, BasePairVisualRenderingOptions> BasePairDefaultRenderingOptionsByValue { get; private set; }
        public BasePairVisualRenderingOptions BasePairDefaultRenderingOptions { get; private set; }

        public void SaveDrawing()
        {
            DrawingManager.Save();
        }

        public void SynchronizeDrawing()
        {
            DrawingManager.Synchronize();
        }

        public void ReloadDrawing()
        {
            OnStructureDrawingDataChanged();
        }

        public void UndoLast()
        {
            StructureElementSelectionMode localSelectionMode = SelectionMode; //cache off the current selection mode
            SelectionMode = StructureElementSelectionMode.None;
            try
            {
                DrawingManager.UndoLast();
            }
            catch (Exception) { }
            if (SelectedElement != null)
            {
                SelectedElement.DeselectElement();
                SelectedElement = null;
            }
            SelectionMode = localSelectionMode; //reset the previous selection mode to pickup any model changes in the selection groups.
            Redraw();
        }

        public void CreateHelix(int fivePrimeStartIdx, int threePrimeEndIdx, int fivePrimeEndIdx)
        {
            if (fivePrimeStartIdx < 0 ||
                threePrimeEndIdx < 0 ||
                fivePrimeEndIdx < fivePrimeStartIdx ||
                threePrimeEndIdx < fivePrimeEndIdx)
                return; //This is an error state for now.

            StructureElementSelectionMode localSelectionMode = SelectionMode; //cache off the current selection mode
            SelectionMode = StructureElementSelectionMode.None;
            int length = (fivePrimeEndIdx - fivePrimeStartIdx) + 1;
            try
            {
                DrawingManager.CreateHelix(fivePrimeStartIdx, threePrimeEndIdx, length);
            }
            catch (Exception) { }
            CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
            CurrentHelixCount = DrawingManager.CurrentHelixCount;
            SelectionMode = localSelectionMode; //reset the previous selection mode.
        }

        public void CreateBasePair(int fivePrimeIdx, int threePrimeIdx)
        {
            if (fivePrimeIdx < 0 ||
                threePrimeIdx < 0 ||
                threePrimeIdx < fivePrimeIdx)
                return; //This is an error state for now.

            StructureElementSelectionMode localSelectionMode = SelectionMode; //cache off the current selection mode
            SelectionMode = StructureElementSelectionMode.None;
            try
            {
                DrawingManager.CreateBasePairs(new List<KeyValuePair<int, int>>() { new KeyValuePair<int, int>(fivePrimeIdx, threePrimeIdx) });
            }
            catch (Exception) { }
            CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
            CurrentHelixCount = DrawingManager.CurrentHelixCount;
            SelectionMode = localSelectionMode; //reset the previous selection mode.
        }

        public void DeleteBasePairsFromRange()
        {
            if ((SelectionMode == StructureElementSelectionMode.Range || SelectionMode == StructureElementSelectionMode.Domain) && SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement; //cache off the selected element
                StructureElementSelectionMode localSelectionMode = SelectionMode; //cache off the current selection mode
                try
                {
                    localSelectedElement.DeleteSelection();
                }
                catch (Exception) { }
                localSelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
                CurrentHelixCount = DrawingManager.CurrentHelixCount;
                SelectionMode = localSelectionMode; //reset the previous selection mode.
            }
        }

        public void DeleteSelectedBasePair()
        {
            if (SelectionMode == StructureElementSelectionMode.BasePair && SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement; //cache off the selected element
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.DeleteSelection();
                }
                catch (Exception) { }
                localSelectedElement = null;
                CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
                CurrentHelixCount = DrawingManager.CurrentHelixCount;
                SelectionMode = StructureElementSelectionMode.BasePair; //Pickup the re-constructed selection set.
            }
        }

        public void DeleteSelectedHelix()
        {
            if (SelectionMode == StructureElementSelectionMode.Helix && SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement; //cache off the selected element
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.DeleteSelection();
                }
                catch (Exception) { }
                localSelectedElement = null;
                CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
                CurrentHelixCount = DrawingManager.CurrentHelixCount;
                SelectionMode = StructureElementSelectionMode.Helix; //Pickup the re-constructed selection set.
            }
        }

        public void SnapSelectedHelix(bool toOpeningBasePair)
        {
            if (SelectionMode == StructureElementSelectionMode.Helix && SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement; //cache off the selected element
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    if (toOpeningBasePair)
                        localSelectedElement.SnapHelixToOpeningBasePair();
                    else
                        localSelectedElement.SnapHelixToClosingBasePair();
                }
                catch (Exception) { }
                localSelectedElement = null;
                SelectionMode = StructureElementSelectionMode.Helix;
            }
        }

        public void ResizeAllStructureElements(double width, double padding, StructureElementSelectionMode mode)
        {
            if (mode == StructureElementSelectionMode.Helix)
            {
                if (width > 0.0)
                {
                    try
                    {
                        Dictionary<int, Point> widthEdits = DrawingManager.ChangeAllHelicesWidth(width);
                        DrawingManager.SaveStructureElementDrawingOperation(widthEdits);
                    }
                    catch (Exception) { }
                }

                if (padding > 0.0)
                {
                    try
                    {
                        Dictionary<int, Point> padEdits = DrawingManager.ChangeAllHelicesPadding(padding);
                        DrawingManager.SaveStructureElementDrawingOperation(padEdits);
                    }
                    catch (Exception) { }
                }
            }
            else if (mode == StructureElementSelectionMode.BasePair)
            {
                if (width > 0.0)
                {
                    try
                    {
                        Dictionary<int, Point> widthEdits = DrawingManager.ChangeAllBasePairsWidth(width);
                        DrawingManager.SaveStructureElementDrawingOperation(widthEdits);
                    }
                    catch (Exception) { }
                }
            }
        }

        public void ResizeSelectedStructureElementTo(double width, double padding)
        {
            if (SelectedElement != null &&
               (SelectionMode == StructureElementSelectionMode.BasePair || SelectionMode == StructureElementSelectionMode.Helix || SelectionMode == StructureElementSelectionMode.Strand))
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                if (localSelectionMode == StructureElementSelectionMode.BasePair)
                {
                    //We ignore the padding argument.
                    localSelectedElement.ResizeTo(width, -1.0);
                }
                else
                {
                    localSelectedElement.ResizeTo(width, padding);
                }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
        }

        public void MoveSelectedStructureElementBy(Point delta)
        {
            if ((SelectionMode == StructureElementSelectionMode.Nucleotide ||
                 SelectionMode == StructureElementSelectionMode.BasePair ||
                 SelectionMode == StructureElementSelectionMode.Helix ||
                 SelectionMode == StructureElementSelectionMode.Strand) &&
                 SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.MoveBy(delta);
                }
                catch (Exception) { }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
            else if (SelectionMode == StructureElementSelectionMode.Range || 
                     SelectionMode == StructureElementSelectionMode.Domain)
            {
                try
                {
                    SelectedElement.MoveBy(delta);
                }
                catch (Exception) { }
            }
        }

        public void MoveSelectedStructureElementTo(Point location)
        {
            if ((SelectionMode == StructureElementSelectionMode.Nucleotide ||
                 SelectionMode == StructureElementSelectionMode.BasePair ||
                 SelectionMode == StructureElementSelectionMode.Helix ||
                 SelectionMode == StructureElementSelectionMode.Strand) &&
                 SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.MoveTo(location);
                }
                catch (Exception) { }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
        }

        public void RotateSelectedStructureElementBy(double angleInDegrees)
        {
            if ((SelectionMode == StructureElementSelectionMode.BasePair ||
                SelectionMode == StructureElementSelectionMode.Helix) &&
                SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.RotateBy(angleInDegrees);
                }
                catch (Exception) { }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
        }

        public void RotateSelectedStructureElementTo(double angleInDegrees)
        {
            if ((SelectionMode == StructureElementSelectionMode.BasePair ||
                SelectionMode == StructureElementSelectionMode.Helix) &&
                SelectedElement != null)
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.RotateTo(angleInDegrees);
                }
                catch (Exception) { }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
        }

        public void EditSelectedStructureElementProperties(StructureElementPropertyChangeSet changeSet)
        {
            if (SelectedElement != null &&
               (SelectionMode != StructureElementSelectionMode.Range && SelectionMode != StructureElementSelectionMode.Domain))
            {
                IStructureElementSelectionGroup localSelectedElement = SelectedElement;
                StructureElementSelectionMode localSelectionMode = SelectionMode;
                SelectedElement.DeselectElement();
                SelectedElement = null;
                SelectionMode = StructureElementSelectionMode.None;
                try
                {
                    localSelectedElement.EditProperties(changeSet, null);
                }
                catch (Exception) { }
                SelectionMode = localSelectionMode;
                SelectedElement = localSelectedElement;
            }
        }

        public void EditSelectedRangeStructureElementProperties(StructureElementPropertyChangeSet changeSet, List<IBioSymbol> filter)
        {
            if (SelectedElement != null &&
               (SelectionMode == StructureElementSelectionMode.Range || SelectionMode == StructureElementSelectionMode.Domain))
            {
                try
                {
                    SelectedElement.EditProperties(changeSet, filter);
                    SelectedElement.DeselectElement();
                }
                catch (Exception) { }
            }
        }

        public void EditAllNucleotideStructureElementProperties(StructureElementPropertyChangeSet changeSet, List<IBioSymbol> filter)
        {
            try
            {
                if (SelectedElement != null &&
                   (SelectionMode == StructureElementSelectionMode.Range || SelectionMode == StructureElementSelectionMode.Domain))
                {
                    SelectedElement.EditProperties(changeSet, filter);
                }
                else
                {
                    if (filter != null && filter.Count > 0)
                        DrawingManager.ApplyPropertyChangesToNucleotidesFiltered(changeSet, filter);
                    else
                        DrawingManager.ApplyPropertyChangesToAllNucleotides(changeSet);
                }
            }
            catch (Exception) { }
        }

        public void EditAllBasePairStructureElementProperties(StructureElementPropertyChangeSet changeSet, List<string> filter)
        {
            try
            {
                if (filter != null && filter.Count > 0)
                    DrawingManager.ApplyPropertyChangesToBasePairsFiltered(changeSet, filter);
                else
                    DrawingManager.ApplyPropertyChangesToAllBasePairs(changeSet);
            }
            catch (Exception) { }
        }

        public void Redraw()
        {
            InvalidateVisual();
        }

        public int NucleotideAtLocation(Point location)
        {
            var ntAtLocation = DrawingManager.ElementSelectionGroups[StructureElementSelectionMode.Nucleotide].Where(grp => (grp.HitTestElement(location) == true)).FirstOrDefault();
            if (ntAtLocation != null)
                return ntAtLocation.ID;
            else
                return -1;
        }

        public bool TrySelectElementContainingNucleotide(int nucleotideIndex)
        {
            if (SelectionGroups != null)
            {
                var newSelection = SelectionGroups.Where(grp => grp.Visual.NucleotideIndices.Contains(nucleotideIndex)).FirstOrDefault();
                
                if (SelectedElement != null)
                {
                    SelectedElement.DeselectElement();
                    SelectedElement = null;
                }

                if (newSelection != null)
                {
                    SelectedElement = newSelection;
                    SelectedElement.SelectElement();
                }
                Redraw();
                return true;
            }
            else { return false; }
        }

        public bool TryHandleSelection(Point location)
        {
            if (SelectionGroups != null)
            {
                var newSelection = SelectionGroups.Where(grp => (grp.HitTestElement(location) == true)).FirstOrDefault();
                if (newSelection == null && SelectedElement == null)
                    return false;

                if (SelectedElement != null)
                {
                    SelectedElement.DeselectElement();
                    SelectedElement = null;
                }

                if (newSelection != null)
                {
                    SelectedElement = newSelection;
                    SelectedElement.SelectElement();
                }
                Redraw();
                return true;
            }
            else { return false; }
        }

        public bool TryStartDragEdit(Point location)
        {
            if (SelectionMode != StructureElementSelectionMode.Range)
            {
                if (!TryHandleSelection(location))
                    return false;
            }
            if (SelectedElement != null)
            {
                SelectedElement.StartDrag(location);
                CaptureMouse();
                return true;
            }
            else { return false; }
        }

        public bool TryUpdateDragEdit(Point location, out Point dragDelta)
        {
            if (SelectedElement != null && SelectedElement.IsDragging)
            {
                dragDelta = SelectedElement.UpdateDrag(location);
                Redraw();
                return true;
            }
            else
            {
                dragDelta = new Point(0, 0);
                return false;
            }
        }

        public bool TryEndDragEdit(Point location, out Point dragDelta)
        {
            if (SelectedElement != null && SelectedElement.IsDragging)
            {
                dragDelta = SelectedElement.EndDrag(location);
                ReleaseMouseCapture();
                Redraw();
                return true;
            }
            else
            {
                dragDelta = new Point(0, 0);
                return false;
            }
        }

        public bool TryStartDragRotation(Point location)
        {
            TryHandleSelection(location);
            if (SelectedElement != null)
            {
                SelectedElement.StartRotation(location);
                CaptureMouse();
                return true;
            }
            else { return false; }
        }

        public bool TryUpdateDragRotation(Point location, out double currentRotation)
        {
            if (SelectedElement != null && SelectedElement.IsRotating)
            {
                currentRotation = SelectedElement.UpdateRotation(location);
                Redraw();
                return true;
            }
            else 
            {
                currentRotation = 0.0;
                return false; 
            }
        }

        public bool TryEndDragRotation(Point location, out double currentRotation)
        {
            if (SelectedElement != null && SelectedElement.IsRotating)
            {
                currentRotation = SelectedElement.EndRotation(location);
                ReleaseMouseCapture();
                Redraw();
                return true;
            }
            else 
            {
                currentRotation = 0.0;
                return false; 
            }
        }

        public SecondaryStructureDrawing()
        {
            Visuals = new VisualCollection(this);
            ChildrenInternal = new List<IStructureElementVisualComponent>();
            DrawingManager = new StructureDrawingManager(this);
            DrawingManager.DrawingBoundsChangedEvent += new EventHandler(OnDrawingBoundsChangedEvent);
            DrawingManager.StructurePropertiesChangedEvent += new EventHandler(OnStructurePropertiesChangedEvent);
            DrawingManager.DirtyStateChangedEvent += new EventHandler(OnDirtyStateChangedEvent);
            NucleotideDefaultRenderingOptions = new NucleotideVisualRenderingOptions();
            NucleotideDefaultRenderingOptionsByValue = new Dictionary<char, NucleotideVisualRenderingOptions>();
            BasePairDefaultRenderingOptions = new BasePairVisualRenderingOptions();
            BasePairDefaultRenderingOptionsByValue = new Dictionary<string, BasePairVisualRenderingOptions>();
            InstallNucleotideVisualRenderingDefaults();
            InstallBasePairVisualRenderingDefaults();
            //PauseEditNotification = false;
            //PauseEditTracking = false;
            BasePairCreatorTool = new BasePairCreationTool() { Drawing = this, IsEnabled = false };
            HelixCreatorTool = new HelixCreationTool() { Drawing = this, IsEnabled = false };
            SelectionTool = new StructureElementSelectionTool() { Drawing = this, IsEnabled = false };
            DrawingTool = new StructureElementDrawingTool() { Drawing = this, IsEnabled = false };
            RotationTool = new StructureElementRotateTool() { Drawing = this, IsEnabled = false };
            StructureModelEditingTools = new List<IStructureDrawingTool>() { BasePairCreatorTool, HelixCreatorTool };
            StructureDrawingTools = new List<IStructureDrawingTool>() { SelectionTool, DrawingTool, RotationTool };
        }

        protected override int VisualChildrenCount
        {
            get { return Visuals.Count; }
        }

        protected override Visual GetVisualChild(int index)
        {
            if (index < 0 || index >= Visuals.Count)
                throw new ArgumentOutOfRangeException("index");

            return Visuals[index];
        }

        protected override Size MeasureOverride(Size availableSize)
        {
            if (DrawingManager != null && Visuals.Count > 0)
            {
                return new Size(DrawingManager.CanvasBounds.Width, DrawingManager.CanvasBounds.Height);
            }
            return base.MeasureOverride(availableSize);
        }

        protected override void OnRender(DrawingContext drawingContext)
        {
            drawingContext.DrawRectangle(DrawingBackground, null, new Rect(RenderSize));
            if(DrawingManager != null && Visuals.Count > 0)
            {
                Pen drawingBoundsPen = new Pen(new SolidColorBrush(Colors.Black), 0.25);
                DashStyle drawingBoundsDashes = new DashStyle() { Dashes = new DoubleCollection() { 2, 6 } };
                drawingBoundsPen.DashStyle = drawingBoundsDashes;
                drawingContext.DrawRectangle(null, drawingBoundsPen, DrawingManager.DrawingBounds);
            }   
        }

        private StructureDrawingManager DrawingManager { get; set; }
        private VisualCollection Visuals { get; set; }
        private List<IStructureElementVisualComponent> ChildrenInternal { get; set; }
        private BasePairCreationTool BasePairCreatorTool { get; set; }
        private HelixCreationTool HelixCreatorTool { get; set; }
        private StructureElementSelectionTool SelectionTool { get; set; }
        private StructureElementDrawingTool DrawingTool { get; set; }
        private StructureElementRotateTool RotationTool { get; set; }
        //private SecondaryStructureDrawingPropertyContext PropertyContextManager { get; set; }
        //private bool PauseEditTracking { get; set; }
        //private bool PauseEditNotification { get; set; }
        
        private static readonly FontFamilyConverter fontFamilyChecker = new FontFamilyConverter();
        private static readonly FontWeightConverter fontWeightChecker = new FontWeightConverter();
        private static readonly FontStyleConverter fontStyleChecker = new FontStyleConverter();
        private static readonly ColorConverter colorChecker = new ColorConverter();

        private IStructureElementSelectionGroup _selectedElement;
        private IStructureElementSelectionGroup SelectedElement
        {
            get { return _selectedElement; }
            set
            {
                _selectedElement = value;
                if (_selectedElement != null)
                {
                    SelectedStructureElementVisualID = _selectedElement.ID;
                    SelectedStructureElementVisual = _selectedElement.Visual;
                    SelectedStructureElementVisualLocation = DrawingManager.GetLocationForNucleotide(SelectedStructureElementVisualID).TopLeft;
                }
                else
                {
                    SelectedStructureElementVisualLocation = new Point(0, 0);
                    SelectedStructureElementVisual = null;
                    if(SelectedStructureElementVisualID != -1)
                        SelectedStructureElementVisualID = -1;
                }
            }
        }

        private List<IStructureElementSelectionGroup> _selectionGroups;
        private List<IStructureElementSelectionGroup> SelectionGroups
        {
            get { return _selectionGroups; }
            set
            {
                if (_selectionGroups != null)
                {
                    foreach (var prevGroup in _selectionGroups)
                    {
                        prevGroup.DeselectElement();
                        prevGroup.MakeElementUnselectable();
                    }
                }

                _selectionGroups = value;
                if (_selectionGroups != null)
                {
                    foreach (var newGroup in _selectionGroups)
                    {
                        newGroup.MakeElementSelectable();
                    }
                }
            }
        }

        private void OnDirtyStateChangedEvent(object sender, EventArgs e)
        {
            if (DrawingManager.DirtyStatus != DrawingDirty)
                DrawingDirty = DrawingManager.DirtyStatus;
        }

        private void OnStructurePropertiesChangedEvent(object sender, EventArgs e)
        {
            CurrentVisibleNucleotideCount = DrawingManager.VisibleNucleotideCount;
            CurrentBasePairCount = DrawingManager.CurrentBasePairCount;
            CurrentHelixCount = DrawingManager.CurrentHelixCount;
        }

        private void OnDrawingBoundsChangedEvent(object sender, EventArgs e)
        {
            if (DrawingManager != null)
            {
                DrawingWidth = DrawingManager.DrawingBounds.Width;
                DrawingHeight = DrawingManager.DrawingBounds.Height;
                CanvasWidth = DrawingManager.CanvasBounds.Width;
                CanvasHeight = DrawingManager.CanvasBounds.Height;
                DrawingOffset = DrawingManager.CanvasPadding;
            }
        }

        private void OnStructureDrawingDataChanged()
        {
            if (StructureData != null)
            {
                //CheckAndSetGlobalNucleotideRenderingProperties();
                DrawingManager.SetStructureDrawingData(StructureData);
                if (DrawingManager.ElementSelectionGroups != null && DrawingManager.ElementSelectionGroups.ContainsKey(StructureElementSelectionMode.Nucleotide))
                {
                    BasePairCreatorTool.NucleotideVisuals = DrawingManager.ElementSelectionGroups[StructureElementSelectionMode.Nucleotide];
                    BasePairCreatorTool.IsEnabled = true;

                    HelixCreatorTool.NucleotideVisuals = DrawingManager.ElementSelectionGroups[StructureElementSelectionMode.Nucleotide];
                    HelixCreatorTool.IsEnabled = true;

                    SelectionTool.IsEnabled = true;
                    DrawingTool.IsEnabled = true;
                    RotationTool.IsEnabled = true;

                    CurrentStructureDrawingTool = SelectionTool;
                }
                CreatePreviewImageSource();
                Redraw();
                RaiseEvent(new RoutedEventArgs(StructureDataChangedEvent, this));
            }
        }

        private void OnSelectionModeChanged()
        {
            if (DrawingManager.ElementSelectionGroups.ContainsKey(SelectionMode))
            {
                SelectionGroups = DrawingManager.ElementSelectionGroups[SelectionMode];
            }
            else
            {
                SelectionMode = StructureElementSelectionMode.None;
                SelectionGroups = null;
            }
            SelectedStructureElementVisualID = -1; //Clear the selection if necessary.
            SelectedStructureElementVisualLocation = new Point(0, 0);
            SelectionTool.OnSelectionModeChanged();
            DrawingTool.OnSelectionModeChanged();
            RotationTool.OnSelectionModeChanged();
        }

        private void OnSelectedElementVisualIDChanged()
        {
            if (SelectedElement != null && SelectedElement.ID != SelectedStructureElementVisualID)
            {
                if (SelectionGroups != null)
                {
                    var targetSelectedElement = SelectionGroups.Where(grp => grp.ID == SelectedStructureElementVisualID).FirstOrDefault();
                    if (targetSelectedElement != null)
                    {
                        SelectedElement.DeselectElement();
                        SelectedElement = targetSelectedElement;
                        SelectedElement.SelectElement();
                    }
                    else
                    {
                        SelectedElement.DeselectElement();
                        SelectedElement = null;
                    }
                }
                else
                {
                    SelectedElement.DeselectElement();
                    SelectedElement = null;
                }
            }
            else
            {
                if (SelectionGroups != null && SelectedStructureElementVisualID >= 0)
                {
                    var targetSelectedElement = SelectionGroups.Where(grp => grp.ID == SelectedStructureElementVisualID).FirstOrDefault();
                    if (targetSelectedElement != null)
                    {
                        SelectedElement = targetSelectedElement;
                        SelectedElement.SelectElement();
                    }
                }
            }

            RaiseEvent(new RoutedEventArgs(SelectedStructureElementVisualIDChangedEvent, this));
        }

        /*private void CheckAndSetGlobalNucleotideRenderingProperties()
        {
            if (StructureData != null)
            {
                //We need to check if any global rendering is specified for the whole sequence
                IExtensibleProperties props = StructureData as IExtensibleProperties;
                if (props != null)
                {
                    if (props.DoesExtendedPropertyExist(StructureModelMetadata.Key))
                    {
                        StructureModelMetadata strmodelMetadata = props.GetExtendedProperty<StructureModelMetadata>(StructureModelMetadata.Key);
                        if (strmodelMetadata.NucleotideRenderingProperties!=null)
                        {
                            try
                            {
                                NucleotideFontFamily = (FontFamily)fontFamilyChecker.ConvertFrom(strmodelMetadata.NucleotideRenderingProperties.Font);
                                NucleotideFontStyle = (FontStyle)fontStyleChecker.ConvertFrom(strmodelMetadata.NucleotideRenderingProperties.FontStyle);
                                NucleotideFontWeight = (FontWeight)fontWeightChecker.ConvertFrom(strmodelMetadata.NucleotideRenderingProperties.FontWeight);
                                NucleotideFontSize = strmodelMetadata.NucleotideRenderingProperties.FontSize;
                                NucleotideRenderingStyle = NucleotideRenderingMode.Text;
                                return;
                            }
                            catch { }
                        }
                        
                    }
                }
            }
        }*/

        private void CreatePreviewImageSource()
        {
            RenderTargetBitmap bitmapRenderer = new RenderTargetBitmap(Convert.ToInt32(DrawingManager.DrawingBounds.Width), Convert.ToInt32(DrawingManager.DrawingBounds.Height), 96, 96, PixelFormats.Default);
            DrawingVisual renderContainer = new DrawingVisual();
            using (DrawingContext ctx = renderContainer.RenderOpen())
            {
                ctx.DrawRectangle(DrawingManager.SnapshotBrush, null, new Rect(0, 0, DrawingManager.DrawingBounds.Width, DrawingManager.DrawingBounds.Height));
            }

            bitmapRenderer.Render(renderContainer);
            bitmapRenderer.Freeze();
            DrawingSnapshot = bitmapRenderer;
        }

        private void RecursivelyRemoveChildVisualsFromVisualCollection(IStructureElementVisualComponent component)
        {
            if (component.Children.Count() <= 0)
                return;

            foreach (var child in component.Children)
            {
                Visuals.Remove(child.VisualComponent);
                RecursivelyRemoveChildVisualsFromVisualCollection(child);
            }
        }

        private void RecursivelyAddChildVisualsToVisualCollection(IStructureElementVisualComponent component)
        {
            if (component.Children.Count() <= 0)
                return;

            foreach (var child in component.Children)
            {
                Visuals.Add(child.VisualComponent);
                RecursivelyAddChildVisualsToVisualCollection(child);
            }
        }

        private void InstallNucleotideVisualRenderingDefaults()
        {
            string builtInLabel = Settings.Default.DefaultNucleotideVisualRenderingOptionSetLabel;
            if (NucleotideDefaultRenderingOptionSets == null)
            {
                NucleotideDefaultRenderingOptions = NucleotideVisualRenderingOptionsData.CreateDefault("").Options;
                NucleotideDefaultRenderingOptionsByValue.Clear();
            }
            else
            {
                var builtInDefaultSet = NucleotideDefaultRenderingOptionSets.FirstOrDefault(od => od.Label.Equals(builtInLabel));
                if (builtInDefaultSet != null && builtInDefaultSet.Options != null)
                    NucleotideDefaultRenderingOptions = builtInDefaultSet.Options;
                else
                    NucleotideDefaultRenderingOptions = NucleotideVisualRenderingOptionsData.CreateDefault("").Options;
                NucleotideDefaultRenderingOptionsByValue.Clear();
                foreach (var otherNtOption in NucleotideDefaultRenderingOptionSets.Where(od => !od.Label.Equals(builtInLabel)))
                {
                    if (!string.IsNullOrEmpty(otherNtOption.Label) && otherNtOption.Options != null && !NucleotideDefaultRenderingOptionsByValue.ContainsKey(otherNtOption.Label[0]))
                        NucleotideDefaultRenderingOptionsByValue.Add(otherNtOption.Label[0], otherNtOption.Options);
                }
            }
        }

        private void InstallBasePairVisualRenderingDefaults()
        {
            string builtInLabel = Settings.Default.DefaultBasePairVisualRenderingOptionSetLabel;
            if (BasePairDefaultRenderingOptionSets == null)
            {
                BasePairDefaultRenderingOptions = BasePairVisualRenderingOptionsData.CreateDefault("").Options;
                BasePairDefaultRenderingOptionsByValue.Clear();
            }
            else
            {
                var builtInDefaultSet = BasePairDefaultRenderingOptionSets.FirstOrDefault(od => od.Label.Equals(builtInLabel));
                if (builtInDefaultSet != null && builtInDefaultSet.Options != null)
                    BasePairDefaultRenderingOptions = builtInDefaultSet.Options;
                else
                    BasePairDefaultRenderingOptions = BasePairVisualRenderingOptionsData.CreateDefault("").Options;
                BasePairDefaultRenderingOptionsByValue.Clear();
                foreach (var otherBpOption in BasePairDefaultRenderingOptionSets.Where(od => !od.Label.Equals(builtInLabel)))
                {
                    if (!string.IsNullOrEmpty(otherBpOption.Label) && otherBpOption.Options != null && !BasePairDefaultRenderingOptionsByValue.ContainsKey(otherBpOption.Label))
                        BasePairDefaultRenderingOptionsByValue.Add(otherBpOption.Label, otherBpOption.Options);
                }
            }
        }
    }
}
